"use strict";



//We delay any pushes during startup, so this can be done whenever as long as it's before startup completes.
startupPostPropertyLoadTasks.push(initialisePushInterfaceFromCpp);

function initialisePushInterfaceFromCpp()
{
	
	var promise = new Promise(
		function(resolve, reject) 
		{
			if(typeof window.cefQuery !== 'undefined')
			{
				var request_id = null;
				
				var theRequest = JSON.stringify(["pushInterface"]);
				
				var request_id = window.cefQuery(
					{
						request: theRequest,
						persistent: true,
						onSuccess: function(response) 
						{
							var theResponse;
							try
							{
								theResponse 	= JSON.parse(response);
								
								var propName 		= theResponse[0];
								var propType 		= theResponse[1];
								var propValueJSON 	= theResponse[2];
								var propRecvFunc 	= theResponse[3];
								
								window[propRecvFunc](propName, propType, propValueJSON);
							}
							catch(e)
							{
								console.log("JSON response could not be parsed: \n" + response);
							}
						},
						onFailure: function(error_code, error_message) 
						{
							throw Error("cefQuery for " + fullMessageArray + " failed with error " + 
										error_code + ": " + error_message);
						}
					});
			}
			resolve();
		});
	
	return promise;
}


var propertyManager = {};

propertyManager.propertyMap = new Object();
propertyManager.propertyMessagingPromises = [];





function resetPropertyList()
{
	var thePromise = propertyManager.resetPropertyList();
	
	thePromise.catch(
		function(e)
		{
			console.error(logStrCpp(e.message));
		});
	
	return thePromise;
}

function hookPropertyToElement(propertyName, propertyType, element)
{
	//The ! (not) operator for bools is just to make life easier on the web side,
	//It shouldn't be passed to C++
	if(propertyType.startsWith("bool") && propertyName.startsWith("!"))
	{
		propertyName = propertyName.substring(1);
	}

	var thePromise = propertyManager.registerPropertyWithElement(element,
																 propertyName,
																 propertyType,
																 setJSStateOfProperty);
	
	//Typeless properties have no state, they are more like events, 
	//so requesting their initial value makes no sense
	if(propertyType !== "")
	{
		thePromise = thePromise.then(
			function()
			{
				return requestPropertyValue(propertyName, propertyType);
			});
	}
	
	propertyManager.propertyMessagingPromises.push(thePromise);
	
	return thePromise;
}


function hookPropertyToFunction(propertyName, propertyType, recvFunction)
{
	//The ! (not) operator for bools is just to make life easier on the web side,
	//It shouldn't be passed to C++
	if(propertyType.startsWith("bool") && propertyName.startsWith("!"))
	{
		propertyName = propertyName.substring(1);
	}

	var thePromise = propertyManager.registerPropertyWithElement(null,
																 propertyName,
																 propertyType,
																 recvFunction );
	
	//Typeless properties have no state, they are more like events, 
	//so requesting their initial value makes no sense
	if(propertyType !== "")
	{
		thePromise = thePromise.then(
			function()
			{
				return requestPropertyValue(propertyName, propertyType);
			});
	}
	
	propertyManager.propertyMessagingPromises.push(thePromise);
	
	return thePromise;
}

//Should only be used when a property is write-only
//ie, we send values to C++, but it will never send them to us.
//This prevents us from trying to initialise the property.
//Typeless properties sorta do this too, but that's only because they can't have any state, 
//they can be read & write but still don't need to be initialised
function registerWriteOnlyProperty(propertyName, propertyType)
{
	//The ! (not) operator for bools is just to make life easier on the web side,
	//It shouldn't be passed to C++
	if(propertyType.startsWith("bool") && propertyName.startsWith("!"))
	{
		propertyName = propertyName.substring(1);
	}

	var thePromise = propertyManager.registerPropertyWithElement(null,
																 propertyName,
																 propertyType,
																 null );
	
	propertyManager.propertyMessagingPromises.push(thePromise);
	
	return thePromise;
}

//We will only need to use this (outside of the functions above) if we have a property that can 
//change without C++ updating us. If a property changes *a lot* and we only care about it's value
//rarely, this may be reasonable. If that isn't the case, we shouldn't ever need this function.
function requestPropertyValue(propertyName, propertyType)
{
	var propertyStartValuePromise = propertyManager.getPropertyValue(	propertyName,
				 														propertyType);

	var thePromise = propertyStartValuePromise.then(
		function(propertyStartValue)
		{
			jsPropertyReceiver(	propertyStartValue[0],
								propertyStartValue[1],
								propertyStartValue[2]);
		});
	return thePromise;
}

//if not undefined, optionalFuncsAndElemsToSkip should be an array that contain any number of elements or 
//functions which you do not want to recieve an update about a change in value.
function setPropertyValue(propertyName, propertyType, propValueArray, optionalFuncsAndElemsToSkip)
{
	if(optionalFuncsAndElemsToSkip !== undefined && !Array.isArray(optionalFuncsAndElemsToSkip))
		throw new Error("optionalFuncsAndElemsToSkip is defined as not undefined or an array, ");
	
	var thePromise = setWriteOnlyPropertyValue(propertyName, propertyType, propValueArray);
	
	thePromise = thePromise.then(
		function()
		{
			jsPropertyReceiver(	propertyName, 
								propertyType, 
								JSON.stringify(propValueArray), 
								optionalFuncsAndElemsToSkip);
		})
	
	thePromise = thePromise.catch(
		function(e)
		{
			var stackFirstLine = parseStackForErrorPosition(e.stack);
			
			console.error(logStrCpp("Error setting property '" + propertyName + "': " + 
									e.message + " occured in: " + stackFirstLine));
			if(debugMode) throw e;
		});
	
	return thePromise;
}

//No JS functions/elements will be informed of the change
function setWriteOnlyPropertyValue(propertyName, propertyType, propValueArray)
{
	var thePromise;
	
	thePromise = propertyManager.setPropertyValue(propertyName, propertyType, propValueArray);
	
	thePromise = thePromise.catch(
		function(e)
		{
			var stackFirstLine = parseStackForErrorPosition(e.stack);
			
			console.error(logStrCpp("Error setting write-only property '" + propertyName + "': " + 
									e.message + " occured in: " + stackFirstLine));
			if(debugMode) throw e;
		});
	
	return thePromise;
}

function awaitOutstandingPropertyPromises()
{
	return Promise.allFinish(propertyManager.propertyMessagingPromises);
}

function logToMainConsole(messageToLog)
{
	var thePromise = propertyManager.logToMainConsole(messageToLog);
	
	thePromise.catch(
		function(e)
		{
			//Don't log to C++, since that would infinite-loop ;)
			console.error(e.message);
		});
}











//Internal functions, don't use them.

propertyManager.getPropertyValue = function(propertyName, propertyType)
{
	var promise = new Promise(
		function(resolve, reject) 
		{
			if(typeof window.cefQuery !== 'undefined')
			{
				propertyManager.CreateCefQueryForRequest(	"getPropertyValue", 
															[	propertyName,
																propertyType	], 
															resolve, 
															reject)
			}
			else
			{
				window.setTimeout(
					function() 
					{
						var propertyArguments = undefined;
				
						try
						{
							propertyArguments = feral.getPropertyValue(	propertyName,
																		propertyType);
						}
						catch(e)
						{
							console.error(logStrCpp("Error getting property '" + propertyName + "': " + e.message));
							
							reject(e);
							return;
						}

						if(propertyArguments === undefined)
						{
							console.warn(logStrCpp(	"feral.getPropertyValue failed for property \"" + 
													propertyName + "\" of type \"" + propertyType + "\""));
							reject(new Error("getPropertyValue failed"));
						}
						else
						{
							resolve(propertyArguments);
						}
					}, 0);
			}
		});
		
	propertyManager.propertyMessagingPromises.push(promise);
	
	return promise;
}

propertyManager.setPropertyValue = function(propertyName, propertyType, propValueArray)
{
	var promise = new Promise(
		function(resolve, reject) 
		{
			if(typeof window.cefQuery !== 'undefined')
			{
				propertyManager.CreateCefQueryForRequest(	"setPropertyValue", 
															[	propertyName,
																propertyType,
																JSON.stringify(propValueArray)	], 
															resolve, 
															reject)
			}
			else
			{
				window.setTimeout(
					function() 
					{
						var functionSucceeded
						
						try
						{
							functionSucceeded = feral.setPropertyValue(	propertyName,
																		propertyType,
																		JSON.stringify(propValueArray));
						}
						catch(e)
						{
							console.error(logStrCpp("Error setting property '" + propertyName + "': " + e.message));
							
							reject(e);
							return;
						}

						if(functionSucceeded === undefined)
						{
							console.warn(logStrCpp(	"feral.setPropertyValue failed for property \"" + propertyName + 
										 			"\" of type \"" + propertyType + 
										 			"\" with propValueArray \"" + JSON.stringify(propValueArray) + "\""));
							
							reject(new Error("setPropertyValue failed"));
						}
						else
						{
							resolve(functionSucceeded);
						}
					}, 0);
			}
		});
	
	propertyManager.propertyMessagingPromises.push(promise);
		
	return promise;
}

propertyManager.resetPropertyList = function()
{
	var promise = new Promise(
		function(resolve, reject) 
		{
			if(typeof window.cefQuery !== 'undefined')
			{
				propertyManager.CreateCefQueryForRequest(	"resetPropertyList", 
															[], 
															resolve, 
															reject)
			}
			else
			{
				window.setTimeout(
					function() 
					{
						var functionSucceeded = feral.resetPropertyList();

						if(functionSucceeded === undefined)
						{
							reject(new Error("resetPropertyList failed"));
						}
						else
						{
							resolve(true);
						}
					}, 0);
			}
		});
		
	return promise;
}

propertyManager.registerProperty = function(propertyName, typePattern, recvFunctionStr) 
{
	var promise = new Promise(
		function(resolve, reject) 
		{
			if(typeof window.cefQuery !== 'undefined')
			{
				propertyManager.CreateCefQueryForRequest(	"registerProperty", 
															[	propertyName,
																typePattern,
																recvFunctionStr   ], 
															resolve, 
															reject)
			}
			else
			{
				window.setTimeout(
					function() 
					{
						var functionSucceeded = feral.registerProperty(	propertyName,
																		typePattern,
																		recvFunctionStr );

						if(functionSucceeded === undefined)
						{
							console.error(logStrCpp("feral.registerProperty failed for property \"" + propertyName + 
													"\" of type \"" + typePattern + 
													"\" with recvFunction \"" + recvFunctionStr + "\""));
							reject(new Error("registerProperty " + propertyName + " failed"));
						}
						else
						{
							resolve(functionSucceeded);
						}
					}, 0);
			}
		});
	
	propertyManager.propertyMessagingPromises.push(promise);
		
	return promise;
}

propertyManager.logToMainConsole = function(messageToLog)
{
	var promise = new Promise(
		function(resolve, reject) 
		{
			if(typeof window.cefQuery !== 'undefined')
			{
				propertyManager.CreateCefQueryForRequest(	"log", 
															[messageToLog], 
															resolve, 
															reject)
			}
			else
			{
				window.setTimeout(
					function() 
					{
						var functionSucceeded = feral.log(messageToLog);
						
						if(functionSucceeded)
							resolve();
						else
							reject(new Error("feral.logToMainConsole failed with message \"" + messageToLog + "\""));
					}, 0);
			}
		});
	
	return promise;
}


propertyManager.registerPropertyWithElement = function(element, propertyName, typePattern, recvFunction)
{
	var thePromise = Promise.resolve();
	
	if(propertyManager.propertyMap[propertyName] === undefined)
	{
		propertyManager.propertyMap[propertyName] = {recvFunctions:[], elements:[], lastType:typePattern};
		
		thePromise = propertyManager.registerProperty(	propertyName,
														typePattern,
														"jsPropertyReceiver" );
	}
	else if(propertyManager.propertyMap[propertyName].lastType !== typePattern)
	{
		//Registering again with the wrong type will get suitably scary and relevant warnings into C++
		thePromise = propertyManager.registerProperty(	propertyName,
														typePattern,
														"jsPropertyReceiver" );
		
		throw new Error("Attempting to register property with differing type patterns!");
	}

	if(recvFunction !== null)
	{
		if(propertyManager.propertyMap[propertyName].recvFunctions.indexOf(recvFunction) === -1)
			propertyManager.propertyMap[propertyName].recvFunctions.push(recvFunction);
	}

	//Null element means this property registration is purely JS, which is fine, 
	//though it needs to cope with pushed values its own way.
	if(element !== null)
	{
		if(propertyManager.propertyMap[propertyName].elements.indexOf(element) === -1)
			propertyManager.propertyMap[propertyName].elements.push(element);
	}
	
	return thePromise;
}

propertyManager.CreateCefQueryForRequest = function(taskName, messageArray, resolve, reject)
{
	var request_id = null;
	var fullMessageArray = messageArray.splice(0);
	fullMessageArray.unshift(taskName);
	
	var theRequest = JSON.stringify(fullMessageArray);
	
	var request_id = window.cefQuery({
		request: theRequest,
		persistent: false,
		onSuccess: function(response) 
		{
			var theResponse;
			try
			{
				theResponse = JSON.parse(response);
			}
			catch(e)
			{
				console.log("JSON response could not be parsed: " +response);
				theResponse = "";
			}
			resolve(theResponse);
		},
		onFailure: function(error_code, error_message) 
		{
			reject(new Error("cefQuery for " + fullMessageArray + " failed with error " + 
							 error_code + ": " + error_message));
		}
	});		
}


//This function may be called by C++ or by the functions above.
function jsPropertyReceiver(propertyName, typePattern, propValueJSON, optionalFuncsAndElemsToSkip)
{
	if(optionalFuncsAndElemsToSkip === undefined) 
		optionalFuncsAndElemsToSkip = [];
	
	var result = true;
	
	// Filter out problematic control characters
	// List pulled from http://www.bennadel.com/blog/2576-testing-which-ascii-characters-break-json-javascript-object-notation-parsing.htm
	propValueJSON = propValueJSON.replace(/[\x01-\x07\x0B\x0E-\x1F]/g, "");

	var propValueArray = JSON.parse(propValueJSON);
	
	if(propertyManager.propertyMap[propertyName] !== undefined)
	{
		var recvFunctions = propertyManager.propertyMap[propertyName].recvFunctions;
		
		result = propertyManager.pushPropToRecvFuncs(	propertyName, 
														typePattern, 
														propValueArray, 
														optionalFuncsAndElemsToSkip,
														recvFunctions);
	}
	else
	{
		//If nothing has hooked the full property+parameter name
		//Parametered properties (PropertyName-Parameter) can
		//be responded to by registering without the parameter.
		
		var propertyNameNoParams = propertyName.split("-")[0];
		
		if(	propertyName !== propertyNameNoParams &&
			propertyManager.propertyMap[propertyNameNoParams] !== undefined )
		{
			var recvFunctions = propertyManager.propertyMap[propertyNameNoParams].recvFunctions;
			
			if(recvFunctions === undefined || recvFunctions.indexOf(setJSStateOfProperty) === -1)
			{
				result = propertyManager.pushPropToRecvFuncs(	propertyName.split("-")[0], 
																typePattern, 
																propValueArray, 
																optionalFuncsAndElemsToSkip,
																recvFunctions);
			}
			else
			{
				//Currently cannot work with setJSStateOfProperty/elements, 
				//since I can't be bothered to rewrite them right now.
				console.error(logStrCpp(
					"Property '" + propertyName + "' not registered, and '" + propertyNameNoParams +
					"' is hooked by setJSStateOfProperty, which cannot respond to parametered properties."));
				result = undefined;
			}
		}
		else
		{
			console.error(logStrCpp("Property '" + propertyName + "' not registered in the JS property map"));
			result = undefined;
		}
	}
	
	//Make the scripting interface return something other than undefined 
	//since some interfaces can't tell the difference between that and an exception 
	return result;
}


propertyManager.pushPropToRecvFuncs = function(	propertyName, 
												typePattern, 
												propValueArray, 
												optionalFuncsAndElemsToSkip,
												recvFunctions)
{
	var result = true;
		
	if(recvFunctions !== undefined && recvFunctions.length > 0)
	{
		for(var i = 0 ; i < recvFunctions.length ; i++ )
		{
			var recvFunction = recvFunctions[i];
			var funcResult = true;
			
			//We don't really care if some are elements, not functions, since they wont be equal
			if(	optionalFuncsAndElemsToSkip.indexOf(recvFunction) === -1)
			{
				if(	recvFunction === setJSStateOfProperty )
					funcResult = setJSStateOfProperty(	propertyName, 
														typePattern, 
														propValueArray, 
														optionalFuncsAndElemsToSkip);
				else
					funcResult = recvFunction(	propertyName, 
												typePattern, 
												propValueArray);
			}
			
			if(result)
				result = funcResult;
		}
	}
	else
	{
		console.error(logStrCpp("No recvFunctions defined for property " + propertyName));
		result = undefined;
	}
	
	return result;
}

